Add Google OAuth login and GAR CI/CD pipeline#1
Conversation
Enable Google OAuth2 login for self-hosted DocuSeal when GOOGLE_CLIENT_ID env var is present. Adds GitHub Actions workflow to build amd64 images and push to Google Artifact Registry via Workload Identity Federation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds conditional Google OAuth2 (OmniAuth + Devise) sign-in, a Users.from_omniauth lookup helper, related routes/locale entries, two gems, and a GitHub Actions workflow to build and push Docker images to Google Artifact Registry on semantic version tags. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/Browser
participant Rails as Rails App
participant Google as Google OAuth
participant DB as Database
User->>Rails: Click "Sign in with Google"
Rails->>Google: Redirect to OAuth authorization
Google->>User: Show consent screen
User->>Google: Authorize
Google->>Rails: Redirect with auth code
Rails->>Google: Exchange code for access token
Google->>Rails: Return access token & user info
Rails->>Rails: OmniauthCallbacksController#google_oauth2
Rails->>DB: Users.from_omniauth(email)
DB->>Rails: Return user record or nil
alt User found and active
Rails->>Rails: Sign in user
Rails->>User: Redirect to dashboard
else User not found or inactive
Rails->>User: Redirect to login with alert
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In @.github/workflows/docker-gar.yml:
- Around line 25-31: The "Create .version file" run block uses ${{
github.ref_name }} directly, which can be an injection vector; instead, add an
environment variable (e.g., REF_NAME) in that job/step set to ${{
github.ref_name }} and reference the shell-safe variable (e.g., "$REF_NAME")
inside the run script (keep the existing github.ref_type check and fallback to
github.sha as an env var if desired) and ensure the variable is quoted when
writing to .version to avoid word-splitting or shell expansion.
- Around line 3-8: Remove the development branch trigger under on.push.branches
(delete the "feature/google-oauth-login" entry) and tighten the tag trigger
under on.push.tags from "*.*.*" to a semver-style pattern such as "v*.*.*" (or
use pattern={{version}} with docker/metadata-action) so only proper semver tags
trigger the workflow; ensure any downstream uses of the tag
(docker/metadata-action inputs) are updated to match the chosen convention.
In `@app/models/user.rb`:
- Line 72: The app boots fail because the User model conditionally includes
devise :omniauthable while routes unconditionally map omniauth_callbacks; make
the guard consistent by introducing a shared early-loaded flag (e.g.
GOOGLE_OAUTH_ENABLED) and use it in both the model line that currently reads
devise :omniauthable, omniauth_providers: [:google_oauth2] and in the routes
where omniauth_callbacks is added (or alternatively wrap the routes'
omniauth_callbacks mapping in the same ENV check); ensure the initializer that
sets GOOGLE_OAUTH_ENABLED is loaded before routes and models so both sides see
the same boolean.
In `@config/locales/i18n.yml`:
- Around line 176-177: The new i18n keys user_not_found and
authentication_failed were added only for English; update all supported locale
YAML files to include equivalent translations for these keys (or add them to the
locale fallback file) so missing-translation errors don't occur; locate the
locale files that mirror i18n.yml (e.g., the other locale YAMLs or the default
fallback locale) and add matching keys with appropriate translated messages, or
confirm/update the app's I18n.fallbacks configuration to ensure these keys
resolve to the English strings when a locale translation is absent.
In `@config/routes.rb`:
- Around line 17-19: The devise_for :users route registers omniauth_callbacks
unconditionally causing boot failure when User is not omniauthable; update the
routes so omniauth_callbacks is only added when ENV['GOOGLE_CLIENT_ID'] (the
same guard used in the User model) is present — conditionally include the
:omniauth_callbacks controller and the :omniauthable routes in the devise_for
:users call (or split into two devise_for calls) so that omniauth_callbacks is
not registered unless ENV['GOOGLE_CLIENT_ID'] is set.
🧹 Nitpick comments (2)
config/initializers/devise.rb (1)
337-342: Consider making the hosted domain configurable via an environment variable.The
hd: 'kencove.com'restriction is hardcoded. If this app is intended to be a reusable self-hosted deployment, extracting it to an env var (e.g.,GOOGLE_HOSTED_DOMAIN) would make it more flexible without code changes.Suggested change
if ENV['GOOGLE_CLIENT_ID'].present? config.omniauth :google_oauth2, ENV.fetch('GOOGLE_CLIENT_ID'), ENV.fetch('GOOGLE_CLIENT_SECRET'), - { hd: 'kencove.com' } + { hd: ENV.fetch('GOOGLE_HOSTED_DOMAIN', 'kencove.com') } endapp/controllers/omniauth_callbacks_controller.rb (1)
6-14: Consider distinguishing "user not found" from "user archived/inactive".Both a missing user (
nil) and an archived/locked user (active_for_authentication? == false) produce the sameuser_not_foundflash message. This can be confusing for an admin debugging login issues for a known but deactivated user.A small improvement:
💡 Suggested change
def google_oauth2 user = Users.from_omniauth(request.env['omniauth.auth']) if user&.active_for_authentication? sign_in_and_redirect(user, event: :authentication) + elsif user + redirect_to new_user_session_path, alert: I18n.t('authentication_failed') else redirect_to new_user_session_path, alert: I18n.t('user_not_found') end end
| user_not_found: User not found. Please contact your administrator. | ||
| authentication_failed: Authentication failed. Please try again. |
There was a problem hiding this comment.
Add translations for non‑English locales.
These new keys are only present in en. If other locales are active, they’ll render as missing translations. Please add equivalent keys for each supported locale or confirm a fallback strategy.
🤖 Prompt for AI Agents
In `@config/locales/i18n.yml` around lines 176 - 177, The new i18n keys
user_not_found and authentication_failed were added only for English; update all
supported locale YAML files to include equivalent translations for these keys
(or add them to the locale fallback file) so missing-translation errors don't
occur; locate the locale files that mirror i18n.yml (e.g., the other locale
YAMLs or the default fallback locale) and add matching keys with appropriate
translated messages, or confirm/update the app's I18n.fallbacks configuration to
ensure these keys resolve to the English strings when a locale translation is
absent.
- Make omniauth_callbacks routes conditional on GOOGLE_CLIENT_ID to prevent boot failure when the env var is missing - Fix shell injection in docker-gar.yml by using env vars instead of direct GitHub expression interpolation in run steps - Remove feature branch trigger and tighten tag pattern to v*.*.* - Make Google hosted domain configurable via GOOGLE_HOSTED_DOMAIN env var Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/docker-gar.yml:
- Around line 55-58: The workflow's tags block uses
"type=raw,value=latest,enable={{is_default_branch}}" which prevents the latest
tag from being applied on tag-triggered runs (github.ref for tags isn't the
default branch); update the docker/metadata-action configuration so latest is
applied for semver/tag pushes—either replace the conditional with the action's
automatic handling by adding "flavor: latest=auto" to the metadata-action
invocation, or make the latest tag unconditional by using
"type=raw,value=latest", or remove the latest line if not desired; edit the tags
block (the line containing type=raw,value=latest and the
enable={{is_default_branch}}) or add the flavor setting to implement your chosen
approach.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/docker-gar.ymlconfig/initializers/devise.rbconfig/routes.rb
🚧 Files skipped from review as they are similar to previous changes (1)
- config/initializers/devise.rb
| tags: | | ||
| type=semver,pattern={{version}} | ||
| type=sha,prefix=,format=short | ||
| type=raw,value=latest,enable={{is_default_branch}} |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
docker metadata-action is_default_branch template variable tag push behavior
💡 Result:
In docker/metadata-action, {{is_default_branch}} is only about the branch ref that triggered the workflow run (i.e., a refs/heads/... run). It returns true only when the workflow was triggered by the repo’s default branch; otherwise false. (github.com)
That means on a tag push run (refs/tags/...), there is no triggering branch ref (e.g., {{branch}} is empty on tag runs), so {{is_default_branch}} is not a reliable way to detect “tag created from default branch”. (github.com)
If your goal is “push latest only for default branch builds”:
- Do it on branch push runs with a conditional raw tag, e.g.
type=raw,value=latest,enable={{is_default_branch}}. (github.com)
If your goal is “push latest on release tags”:
- Rely on the action’s built-in latest tag handling (
flavor: latest=autoby default), which generateslatestfor tag/semver/match-style tag strategies. (github.com)
Finally, while {{base_ref}} may sometimes show something on tag push payloads, the action explicitly warns it doesn’t always return the expected branch on tag pushes and is not recommended to rely on for this purpose. (github.com)
Citations:
- 1: https://github.com/docker/metadata-action
- 2: https://github.com/docker/metadata-action
- 3: https://github.com/docker/metadata-action
- 4: https://github.com/docker/metadata-action?utm_source=openai
- 5: https://github.com/docker/metadata-action
The latest tag may never be applied on tag-triggered workflows.
When this workflow is triggered by a tag push, github.ref is refs/tags/v*.*.*, not a branch ref. The is_default_branch template in docker/metadata-action evaluates whether the current ref is the default branch—which is false for tag pushes. This means the latest tag will never be applied.
If you want latest to be applied when tagging, consider one of these approaches:
- Use the action's built-in latest tag handling by setting
flavor: latest=auto(applieslatestfor tag/semver tag strategies) - Always apply
lateston semver tags:type=raw,value=latest - Remove the
latesttag line if it's not needed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/docker-gar.yml around lines 55 - 58, The workflow's tags
block uses "type=raw,value=latest,enable={{is_default_branch}}" which prevents
the latest tag from being applied on tag-triggered runs (github.ref for tags
isn't the default branch); update the docker/metadata-action configuration so
latest is applied for semver/tag pushes—either replace the conditional with the
action's automatic handling by adding "flavor: latest=auto" to the
metadata-action invocation, or make the latest tag unconditional by using
"type=raw,value=latest", or remove the latest line if not desired; edit the tags
block (the line containing type=raw,value=latest and the
enable={{is_default_branch}}) or add the flavor setting to implement your chosen
approach.
Summary
GOOGLE_CLIENT_IDenv var)linux/amd64images and push to Google Artifact RegistryImage
Tags: semver (
1.2.3), short SHA (abc1234),latest(on default branch)Google OAuth Changes
Gemfileomniauth-google-oauth2,omniauth-rails_csrf_protectionapp/models/user.rb:omniauthabledevise moduleconfig/initializers/devise.rbgoogle_oauth2provider config withhd: kencove.comconfig/routes.rbomniauth_callbacksto devise routesapp/controllers/omniauth_callbacks_controller.rblib/users.rbconfig/locales/i18n.ymlGAR CI/CD Workflow
.github/workflows/docker-gar.yml— triggers on semver tags +feature/google-oauth-loginbranch.GCP Resources Created
us-central1-docker.pkg.dev/kencove-prod/docuseal(docker)projects/103143301688/locations/global/workloadIdentityPools/github-actionsgithub— issuertoken.actions.githubusercontent.com, scoped tokencoveorggithub-actions-docuseal@kencove-prod.iam.gserviceaccount.comartifactregistry.writeron docuseal repo; WIF binding scoped tokencove/docusealGCP_WORKLOAD_IDENTITY_PROVIDER,GCP_SERVICE_ACCOUNTset on repoEnvironment Variables for Deployment
To enable Google OAuth in the cluster, set these on the DocuSeal deployment:
Google OAuth App Setup (Google Cloud Console)
kencove-prodGCP projecthttps://<your-docuseal-domain>/users/auth/google_oauth2/callbackBehavior
/sign_in, restricted to@kencove.comaccountsTest plan
feature/google-oauth-loginus-central1-docker.pkg.dev/kencove-prod/docuseal/docusealGOOGLE_CLIENT_IDsetGOOGLE_CLIENT_ID(no regression)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores